import {UploadItem,UploadStatus} from './models'

export function createUploadItem(fileName:string,fileSize:number,file:File,status:UploadStatus=UploadStatus.Waiting,progress:number=0):UploadItem{
        return new UploadItem(
            fileName,
            fileSize,
            file,
            status,
            progress,
        )
    }

function generateEmptyFile(){
    let blob=new Blob(['Empty File!'])
    return new File([blob],'Empty File')
}

function generateFakeFile(file_name:string,file_size:number){
    console.log(file_size)
    let buffer= new ArrayBuffer(file_size)
    let blob=new Blob([buffer])
    return new File([blob],file_name)
}
const KB=1024
const MB=1024*1024
const GB=MB*1024


export function generateFakeUploadItems(count:number=5,min_size:number=1*KB, max_size:number=100*MB,random=false){
    const res:UploadItem[]=[]
    for(let i=0; i< count;i++){
        let fileName=`sample${i}.dat`
        let fileSize=0
        if (random){
            fileSize=Math.random()*(max_size-min_size)+min_size
        }else{
            fileSize=min_size+(max_size-min_size)/(count-1)*i
        }
        fileSize=Math.floor(fileSize)
        let item=new UploadItem(
            fileName,
            fileSize,
            generateFakeFile(fileName,fileSize),
            UploadStatus.Waiting,
        )
        res.push(item)
    }
    return res
}
function randomProgress(item:UploadItem){
      item.progress+=Math.random()/20
      if (item.progress>1){
        item.progress=1
      }
  }
export function fakeUpload(item:UploadItem){
    let promise =new Promise((resolve)=>{
        item.status=UploadStatus.Uploading
        let timer=setInterval(()=>{
            randomProgress(item)
            if (item.progress>=1){
                item.status=UploadStatus.Completed
                clearInterval(timer)
                resolve(0)
            }
            if (item.status!=UploadStatus.Uploading){
                clearInterval(timer)
                resolve(0)
            }
    },1000)

    })
    return promise
}

interface UploadRecord{
    fid:number,
    state:FileState,
    message?:string;
    offset:number,
}

async function request_upload(item:UploadItem){
    const reqUrl='/api/files/upload'
    const body={
        file_name:item.fileName,
        file_size:item.fileSize,
        reupload:item.reupload??false,
    }
    // console.log(body)
    const res= await fetch(reqUrl,{
        method:'post',
        headers:{
            'Content-Type':'application/json;charset=utf-8'
        },
        body:JSON.stringify(body)
    })

    if (!res.ok){
        item.status=UploadStatus.Error
        // console.log( `Request uploading Failed[${res.status}]: ${item.fileName}`)
        item.detail="HTTP Error:"+res.status
    }
    const raw_json=await res.json()
    return JSON.parse(raw_json) as UploadRecord

}

enum ChunkState{
    Accepted=0,
    Rejected=1,
    Failed=2,
    Ended=3,
}

interface ChunkRecord{
    fid:number,
    offset:number,
    state:ChunkState,
    message?:string,
}

async function* buildChunkGenerator(file:File,uploadRecord:UploadRecord,chunkSize:number=1*MB){
    let header=new ArrayBuffer(12)
    let header_view= new Int32Array(header)
    header_view[0]=uploadRecord.fid
    const fileLength=file.size
    // console.log('FileSize, Offset:',fileLength,uploadRecord.offset)
    while(uploadRecord.offset<fileLength){
        header_view[1]=uploadRecord.offset
        let length=Math.min(chunkSize,fileLength-uploadRecord.offset)
        header_view[2]=length
        let buffer=new ArrayBuffer(12+length)
        let buffer_view=new Uint8Array(buffer)
        const data= await (file.slice(uploadRecord.offset,uploadRecord.offset+length).arrayBuffer())
        buffer_view.set(new Uint8Array(header))
        buffer_view.set(new Uint8Array(data),12)
        // console.log('Generat chunk:',{fid:uploadRecord.fid,offset:uploadRecord.offset, length})
        yield buffer
        uploadRecord.offset+=length               
    }
    header_view[1]=0
    header_view[2]=0
    // console.log('Generate End chunk:',uploadRecord.fid)
    yield header
}
function can_upload(item:UploadItem){
    console.log('Check status:',UploadStatus[item.status])
    return !(item.status==UploadStatus.Stop)
}
async function uploadLargeFile(item:UploadItem,fileState:UploadRecord){
    if (can_upload(item)==false) return
    item.status=UploadStatus.Uploading
    const api='ws://192.168.43.204:8001/ws/upload'
    const offset=fileState.offset
    if (offset<0){
        item.status=UploadStatus.Error
        item.detail='Invalid offset:'+fileState.offset
        return
    }
    if (!WebSocket){
        item.status=UploadStatus.Error
        item.detail='Error: Websocket not supported!'
        // alert('Websocket is not supported!')
        return
    }
    const ws=new WebSocket(api)
    const chunkGenerator=buildChunkGenerator(item.file,fileState)
    ws.onopen=async ()=>{
        // console.log('Upload WS opened...')
        let chunk=await chunkGenerator.next()
        ws.send(chunk.value as ArrayBuffer)
        // console.log('Send first Chunk')
    }
    ws.onmessage=async function(event){
        if (!can_upload(item)) {// 暂停功能
            ws.close()
            return
        }
        let chunkState=(JSON.parse(event.data))
        chunkState=JSON.parse(chunkState) as ChunkRecord
        // console.log('Accept ws message:',event.data)
        // console.log('Parsed chunk result:',chunkState)
        if (chunkState.state==ChunkState.Ended) {
            // console.log('Complete chunk upload:',fileState.fid)
            // ws.close()
            item.status=UploadStatus.Completed
            ws.close()
            return
        }
        if (chunkState.state==ChunkState.Accepted){            
            item.progress=fileState.offset/item.fileSize
            // console.log('Upload chunk ok:',fileState.fid,'progress:',item.progress)
            let chunk=await chunkGenerator.next()
            ws.send(chunk.value as ArrayBuffer)
        }
        else{
            // console.log('Error chunk upload:',chunkState)
            item.status=UploadStatus.Error
            item.detail=chunkState.message
            ws.close()
        }
    }

    return new Promise(resolve=>{
        ws.onclose=()=>{
            console.log('Ws closed')
            resolve(null)
        }
    })
}

interface SmallFileUploadResult{
    file_path:string;
    error?:string;
}

async function uploadSmallFile(item:UploadItem){
    item.status=UploadStatus.Uploading
    const api='/api/files/upload/small'
    const formData= new FormData()
    formData.append('file',item.file)
    const res=await fetch(api,{
        method:'post',
        body:formData,
    })
    const result:SmallFileUploadResult=await res.json()
    if (res.ok) {
        item.status=UploadStatus.Completed
        // item.detail=''+result.file_path
    }else{
        // console.log(`Upload failed[${res.status}]:${item.fileName}`)
        item.status=UploadStatus.Error
        item.detail='Error:'+result.error??'unknown error'
    }
}

enum FileState{
    Completed=0,
    Broken=1,
    New=2,
}

export async function uploadFile(item:UploadItem, max_size:number=1024*1024*1){
    if (can_upload(item)==false) return
    item.status=UploadStatus.Uploading
    let uploadRecord=await request_upload(item)
    if (uploadRecord.state==FileState.Completed) {
        const flag=confirm(`[${item.fileName}] has uploaded, reupload?`)
        if (flag) {
            item.reupload=true
            uploadRecord=await request_upload(item)
            console.log('Reupload:',item)
        }else{
            item.status=UploadStatus.Completed
            return
        }
    }
    // console.log('UploadRecord:',uploadRecord)
    if (item.fileSize<max_size) {
        await uploadSmallFile(item)
    }else{
        await uploadLargeFile(item,uploadRecord)
    }


}
